package uncertainty;

import java.util.Iterator;
import java.util.Stack;

import tools.DoubleVector;
import tools.IntVector;
import tools.Matrix;
import tools.Vector;

public class BertSimasUncertSet extends UncertaintySet {
	
	protected Vector<Double> med;
	protected Vector<Double> dev;
	protected int gamma;
	
	private Vector<Double> transD(Vector<Integer> entry){
		Vector<Double> out = new DoubleVector(entry.getDim());
		for(int i = 0; i < entry.getDim(); i++){
			if(entry.get(i).intValue() == 1)out.set(i, new Double(med.get(i).doubleValue() + dev.get(i).doubleValue()));
			else out.set(i, new Double(med.get(i).doubleValue()));
		}
		return out;
	}
	
	private Vector<Integer> transI(Vector<Double> entry){
		Vector<Integer> out = new IntVector(entry.getDim());
		for(int i = 0; i < entry.getDim(); i++){
			if(entry.get(i).doubleValue() > med.get(i)) out.set(i, new Integer(1));
			else out.set(i, new Integer(0));
		}
		return out;
	}
	
	private class InnerIterator implements Iterator<Vector<Integer>>{
		class EnumSubProb{
			Vector<Integer> elem;
			int h;
			int p;
			
			EnumSubProb(Vector<Integer> elem, int h, int p){
				this.elem = elem;
				this.p = p;
				this.h = h;
			}

			public EnumSubProb copy() {
				return new EnumSubProb(elem.copy(), h, p);
			}
		}
		
		int bg;
		int end;
		int usd;
		int left;
		EnumSubProb current;
		EnumSubProb nextelem;
		Stack<EnumSubProb> q;
		
		public InnerIterator(int start, int fin, int used){
			bg = start;
			end = fin;
			usd = used;
			left = gamma - usd;
			q = new Stack<EnumSubProb>();
			Vector<Integer> rootv = new IntVector(dim);
			EnumSubProb root = new EnumSubProb(rootv, 0, bg);
			if(left >= 0){  
				q.add(root);
				enumerateNext();
			}
			
		}
		
		private boolean branch(EnumSubProb e){
			EnumSubProb e1 = e.copy();
					
			if(e.p++ < end){
				if(e.h < left){
					e1.elem.set(e1.p++, 1);
					e1.h++;
					q.add(e1);
					return true;
				}
				else{
					return false;
				}
			}
			return false;
		}
		
		private boolean enumerateNext(){
			while(!q.isEmpty()){
				current = q.peek();
				if(!branch(current) && current != null){
					q.pop();
					//if(current.elem.sum(dim).intValue() == gamma){
						nextelem = current.copy();
						return true;
					//}	
				}
			}
			nextelem = current = null;
			return false;
		}
		
		@Override
		public boolean hasNext() {
			return nextelem != null;
		}

		@Override
		public Vector<Integer> next() {
			return current.elem;
		}

		@Override
		public void remove() {
			enumerateNext();
		}
		
		public void finalize(){
			current = null;
			nextelem = null;
			q = null;
			System.gc();
		}
	}	
	
	private Iterator<Vector<Integer>> getIterator(final int start, final int fin, final int used){
		return new InnerIterator(start, fin, used);
	}
	

	public BertSimasUncertSet(int dim, int gamma, Vector<Double> medians, Vector<Double> deviations) {
		super(dim);
		this.med = medians;
		this.dev = deviations;
		this.gamma = gamma;
	}

	@Override
	public Iterator<Vector<Double>> extremePoints() {
		return new Iterator<Vector<Double>>(){
			Iterator<Vector<Integer>> it = getIterator(0, dim, 0);
			
			public boolean hasNext() {
				return it.hasNext();
			}

			public Vector<Double> next() {
				return transD(it.next());
			}

			public void remove() {
				it.remove();
			}
			
		};
	}


	@Override
	public Matrix constrains() {
		// TODO 
		return null;
	}

	@Override
	public Matrix dualConstrains() {
		// TODO 
		return null;
	}

	@Override
	public Vector<Double> max(Vector<Double> obj, double objv) {
		Vector<Integer> coef = new IntVector(dim);
		Vector<Double> c = new DoubleVector(dim);
		Vector<Double> ret = new DoubleVector(dim);
		double aux;
		
		for(int i = 0; i < dim; i++){
			c.set(i, med.get(i)*obj.get(i));
			coef.set(i,i);
		}
		
		for(int i = 0; i < dim; i++){
			for(int j = 0; j < dim; j++){
				if(c.get(i) < c.get(j)){
					aux = c.get(i).doubleValue();
					c.set(i, c.get(j));
					c.set(j, new Double(aux));
					aux = coef.get(i).doubleValue();
					coef.set(i, coef.get(j));
					coef.set(j, new Integer((int)aux));
				}
			}
		}
		
		for(int i = 0; i < dim; i++){
			ret.set(i, med.get(i));
		}
		
		for(int i = 0; i < gamma; i++){
			ret.set(coef.get(i).intValue(), dev.get(coef.get(i).intValue())+ ret.get(coef.get(i).intValue()));
		}
		
		return ret;
	}

	@Override
	public Vector<Double> min(Vector<Double> obj, double objv) {
		Vector<Integer> coef = new IntVector(dim);
		Vector<Double> c = new DoubleVector(dim);
		Vector<Double> ret = new DoubleVector(dim);
		double aux;
		
		for(int i = 0; i < dim; i++){
			c.set(i, med.get(i)*obj.get(i));
			coef.set(i,i);
		}
		
		for(int i = 0; i < dim; i++){
			for(int j = 0; j < dim; j++){
				if(c.get(i) > c.get(j)){
					aux = c.get(i).doubleValue();
					c.set(i, c.get(j));
					c.set(j, new Double(aux));
					aux = coef.get(i).doubleValue();
					coef.set(i, coef.get(j));
					coef.set(j, new Integer((int)aux));
				}
			}
		}
		
		for(int i = 0; i < dim; i++){
			ret.set(i, med.get(i));
		}
		
		for(int i = 0; i < gamma; i++){
			ret.set(coef.get(i).intValue(), dev.get(coef.get(i).intValue()) + ret.get(coef.get(i).intValue()));
		}
		
		return ret;
	}

	@Override
	public Iterator<Vector<Double>> eqClassOf(final Vector<Double> xi, final int i) {
		return new Iterator<Vector<Double>>(){
			
			Vector<Integer> vxi = transI(xi);
			Iterator<Vector<Integer>> it = getIterator(i, dim, xi.sum(i).intValue());

			@Override
			public boolean hasNext() {
				return it.hasNext();
			}

			@Override
			public Vector<Double> next() {
				Vector<Integer> e = it.next();
				Vector<Integer> ex = vxi.copy();
				for(int j = i; j < dim; j++)if(e.get(j) == 1) ex.set(j, e.get(j));
				return transD(ex);
			}

			@Override
			public void remove() {
				it.remove();
			}
			
		};
	}

	@Override
	public Iterator<Vector<Double>>[] eqClasses(int i) {
		//TODO
		return null;
	}
	
	@Override
	public int sizeEqClasses(int i) {
		//TODO
		return 0;
	}
	
	public void finalize(){
		med = null;
		dev = null;
		System.gc();
	}

}
